package org.study.minio.boot.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.google.common.io.ByteStreams;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.*;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.study.minio.boot.config.MinioConstant;
import org.study.minio.boot.config.MinioProperties;
import org.study.minio.boot.model.FileZipVo;
import org.study.minio.boot.util.MinioUtils;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 作用描述
 *
 * @author doveylovey
 * @version v1.0.0
 * @email 1135782208@qq.com
 * @date 2023年10月07日
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/minio")
public class MinioController {
    private final MinioProperties minioProperties;

    /**
     * 上传文件。多文件上传参数：@RequestParam("files") MultipartFile[] files
     *
     * @param file 文件
     **/
    @PostMapping("/uploadFile")
    public String uploadFile(@RequestParam("file") MultipartFile file) throws Exception {
        String filename = file.getOriginalFilename();
        String fileContentType = file.getContentType();
        // 单位是字节，除以1024是kb
        long size = file.getSize();
        int available = file.getInputStream().available();
        log.info("文件信息：contentType={}，filename={}，size={}，available={}", fileContentType, filename, size, available);
        // 校验
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传的文件不能为空");
        }
        // 文件类型、大小校验
        String suffix = MinioUtils.getMultipartFileSuffix(file);
        if (StringUtils.isBlank(suffix)) {
            throw new IllegalArgumentException("上传的文件没有扩展名");
        }
        MinioUtils.checkFileSizeAndSuffix(file.getSize(), suffix.toLowerCase());
        // 上传的文件以月为目录归档存放
        String folder = DateUtil.format(LocalDateTime.now(), "yyyy-MM");
        String objectName = folder + "/" + filename;
        String contentType = MediaTypeFactory.getMediaType(filename).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        String url = MinioUtils.uploadFile(MinioConstant.BUCKET_TEST, file, objectName, contentType);
        return url;
    }

    /**
     * 上传文件：同时生成一张缩略图
     *
     * @param file 文件
     **/
    @PostMapping("/uploadFile2")
    public Map<String, String> uploadFile2(@RequestParam("file") MultipartFile file) throws Exception {
        String filename = file.getOriginalFilename();
        String fileContentType = file.getContentType();
        // 单位是字节，除以1024是kb
        long size = file.getSize();
        int available = file.getInputStream().available();
        log.info("文件信息：contentType={}，filename={}，size={}，available={}", fileContentType, filename, size, available);
        // 校验
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传的文件不能为空");
        }
        // 文件类型、大小校验
        String suffix = MinioUtils.getMultipartFileSuffix(file);
        if (StringUtils.isBlank(suffix)) {
            throw new IllegalArgumentException("上传的文件没有扩展名");
        }
        Map<String, String> result = new HashMap<>();

        MinioUtils.checkFileSizeAndSuffix(file.getSize(), suffix.toLowerCase());
        // 上传的文件以月为目录归档存放
        String folder = DateUtil.format(LocalDateTime.now(), "yyyy-MM");
        String tempFilename = IdUtil.getSnowflakeNextIdStr();
        String objectName = folder + "/" + tempFilename + suffix;
        String url = MinioUtils.uploadFile(MinioConstant.BUCKET_TEST, file, objectName);
        result.put("srcUrl", url);
        // 生成缩略图并上传
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            Thumbnails.of(file.getInputStream())
                    .size(640, 480)
                    // .outputFormat("jpg")
                    .outputFormat(suffix.substring(1))
                    .toOutputStream(outputStream);
            String objectName2 = folder + "/" + tempFilename + "_thumbnail" + suffix;
            String thumbnailUrl = MinioUtils.uploadFile(MinioConstant.BUCKET_TEST, objectName2, outputStream.toByteArray());
            result.put("thumbnailUrl", thumbnailUrl);
        }
        return result;
    }

    /**
     * 下载文件
     *
     * @param objectName 待下载文件名称
     **/
    @GetMapping("/downloadFile1")
    public void downloadFile1(HttpServletRequest request, HttpServletResponse response, @RequestParam String objectName) {
        String fileName = objectName.substring(objectName.lastIndexOf("/") + 1);
        String userAgent = request.getHeader("User-Agent").toLowerCase();
        try (InputStream is = MinioUtils.getObject(MinioConstant.BUCKET_TEST, objectName);
             OutputStream os = response.getOutputStream()) {
            String downloadFileName = null;
            if (userAgent.contains("msie") || userAgent.contains("trident") || StringUtils.contains(userAgent, "edge") || userAgent.contains("like gecko")) {
                // IE浏览器、以IE为内核的浏览器
                downloadFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
            } else {
                // 非IE浏览器
                downloadFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            }
            response.reset();
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=" + downloadFileName);
            // 使用工具类直接将文件的字节复制到响应输出流中
            FileCopyUtils.copy(is, os);
        } catch (Exception ex) {
            log.error("下载文件异常", ex);
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            try (OutputStream os = response.getOutputStream()) {
                os.write("下载文件异常".getBytes(StandardCharsets.UTF_8));
            } catch (IOException io) {
            }
        }
    }

    /**
     * 下载文件
     *
     * @param objectName 待下载文件名称
     **/
    @GetMapping("/downloadFile2")
    public ResponseEntity downloadFile2(HttpServletRequest request, HttpServletResponse response, @RequestParam String objectName) {
        String fileName = objectName.substring(objectName.lastIndexOf("/") + 1);
        String userAgent = request.getHeader("User-Agent").toLowerCase();
        try (InputStream is = MinioUtils.getObject(MinioConstant.BUCKET_TEST, objectName)) {
            String downloadFileName = null;
            if (userAgent.contains("msie") || userAgent.contains("trident") || StringUtils.contains(userAgent, "edge") || userAgent.contains("like gecko")) {
                // IE浏览器、以IE为内核的浏览器
                downloadFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
            } else {
                // 非IE浏览器
                downloadFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            }
            // 设置响应头，把文件名称放入响应头中，确保文件可下载
            HttpHeaders headers = new HttpHeaders();
            headers.set("Content-Disposition", "attachment;filename=" + downloadFileName);
            // 设置内容类型为application/octet-stream，二进制流
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            // 获取文件对象的字节码文件
            byte[] bytes = is.readAllBytes();
            // 返回一个字节码类型的响应，同时设置了响应头和状态码。链式调用的方式：ResponseEntity.ok().headers(headers).body(bytes)
            return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
        } catch (Exception ex) {
            log.error("下载文件异常", ex);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_HTML);
            return ResponseEntity.ok().headers(headers).body("下载文件异常".getBytes(StandardCharsets.UTF_8));
        }
    }

    /**
     * 预览文件
     *
     * @param objectName 待预览文件名称
     **/
    @GetMapping("/previewFile1")
    public void previewFile1(HttpServletRequest request, HttpServletResponse response, @RequestParam String objectName) {
        String fileName = objectName.substring(objectName.lastIndexOf("/") + 1);
        String userAgent = request.getHeader("User-Agent").toLowerCase();
        try (InputStream inputStream = MinioUtils.getObject(MinioConstant.BUCKET_TEST, objectName);
             OutputStream outputStream = response.getOutputStream()) {
            String previewFileName = null;
            if (userAgent.contains("msie") || userAgent.contains("trident") || StringUtils.contains(userAgent, "edge") || userAgent.contains("like gecko")) {
                // IE浏览器、以IE为内核的浏览器
                previewFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
            } else {
                // 非IE浏览器
                previewFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            }
            String contentType = MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
            // 非常重要
            response.reset();
            response.setContentType(contentType);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setHeader("Content-Disposition", "inline; filename=" + previewFileName);
            // 使用工具类直接将文件的字节复制到响应输出流中
            FileCopyUtils.copy(inputStream, outputStream);
        } catch (Exception ex) {
            log.error("预览文件异常", ex);
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            try (OutputStream os = response.getOutputStream()) {
                os.write("预览文件异常".getBytes(StandardCharsets.UTF_8));
            } catch (IOException io) {
            }
        }
    }

    /**
     * TODO 预览文件
     *
     * @param objectName 待预览文件名称
     **/
    @GetMapping("/previewFile2")
    public ResponseEntity previewFile2(HttpServletRequest request, HttpServletResponse response, @RequestParam String objectName) {
        // public static ResponseEntity<?> getResponseEntity(byte[] fileContent, String contentDispositionType, String originalFileName){
        String fileName = objectName.substring(objectName.lastIndexOf("/") + 1);
        String userAgent = request.getHeader("User-Agent").toLowerCase();
        try (InputStream inputStream = MinioUtils.getObject(MinioConstant.BUCKET_TEST, objectName);
             OutputStream outputStream = response.getOutputStream()) {
            String previewFileName = null;
            if (userAgent.contains("msie") || userAgent.contains("trident") || StringUtils.contains(userAgent, "edge") || userAgent.contains("like gecko")) {
                // IE浏览器、以IE为内核的浏览器
                previewFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
            } else {
                // 非IE浏览器
                previewFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            }

            byte[] body = new byte[inputStream.available()];
            inputStream.read(body);

            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.setContentType(MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM));
            httpHeaders.setContentLength(inputStream.available());
            httpHeaders.setContentDisposition(ContentDisposition.builder("inline").filename(previewFileName).build());
            // httpHeaders.setCacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES));
            httpHeaders.setCacheControl(CacheControl.noCache());

            return ResponseEntity.ok().headers(httpHeaders).body(body);
            // return ResponseEntity.ok().headers(httpHeaders).body(inputStream.readAllBytes());
            // return ResponseEntity.ok().headers(httpHeaders).body(new InputStreamResource(inputStream));
        } catch (Exception ex) {
            log.error("预览文件异常", ex);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.TEXT_HTML);
            return ResponseEntity.ok().headers(headers).body("预览文件异常".getBytes(StandardCharsets.UTF_8));
        }
    }

    /**
     * 删除文件
     *
     * @param objectName 待删除文件名称
     **/
    @DeleteMapping("/removeFile")
    public boolean removeFile(@RequestParam String objectName) throws Exception {
        boolean removeFile = MinioUtils.removeFile(MinioConstant.BUCKET_TEST, objectName);
        return removeFile;
    }

    /**
     * 拷贝文件
     *
     * @param sourcePath   原始路径
     * @param targetObject 目标对象
     **/
    @PostMapping("/copyFile")
    public String copyFile(@RequestParam String sourcePath, @RequestParam String targetObject) {
        try (InputStream inputStream = MinioUtils.getObject(MinioConstant.BUCKET_TEST, sourcePath.replace(minioProperties.getEndpoint() + "/" + MinioConstant.BUCKET_TEST, ""));
             InputStream uploadStream = new ByteArrayInputStream(ByteStreams.toByteArray(inputStream))) {
            String url = MinioUtils.uploadFile(MinioConstant.BUCKET_TEST, targetObject, uploadStream);
            return url;
        } catch (Exception ex) {
            throw new RuntimeException("获取文件失败");
        }
    }

    /**
     * 按文件夹打包zip下载
     *
     * @param folder   文件夹名
     * @param response
     **/
    @GetMapping("/downloadFolderZip")
    public String downloadFolderZip(@RequestParam String folder, HttpServletResponse response) throws Exception {
        boolean folderExists = MinioUtils.isFolderExist(MinioConstant.BUCKET_TEST, folder);
        if (!folderExists) {
            throw new RuntimeException("当前文件夹不存在");
        }
        List<String> allObjectNameInFolder = MinioUtils.listObjectsName(MinioConstant.BUCKET_TEST, folder, false);
        List<FileZipVo> fileVoList = Lists.newArrayList();
        for (String objectName : allObjectNameInFolder) {
            FileZipVo fileZipVo = new FileZipVo();
            fileZipVo.setFilename(objectName.replace(folder, ""));
            try (InputStream object = MinioUtils.getObject(MinioConstant.BUCKET_TEST, objectName)) {
                byte[] bytes = IOUtils.toByteArray(object);
                fileZipVo.setData(bytes);
                fileVoList.add(fileZipVo);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ZipOutputStream zipOut = new ZipOutputStream(baos)) {
            fileVoList.forEach(fileVo -> {
                try (InputStream inputStream = new ByteArrayInputStream(fileVo.getData())) {
                    ZipEntry zipEntry = new ZipEntry(fileVo.getFilename());
                    zipOut.putNextEntry(zipEntry);
                    byte[] bytes = new byte[1024 * 1024];
                    int length;
                    while ((length = inputStream.read(bytes)) >= 0) {
                        zipOut.write(bytes, 0, length);
                    }
                    zipOut.closeEntry();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            zipOut.finish();
            byte[] zipData = baos.toByteArray();
            InputStream inputStream = new ByteArrayInputStream(zipData);
            String objectName = "publish/" + folder + "(" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ").zip";
            String url = MinioUtils.uploadFile(MinioConstant.BUCKET_TEST, objectName, inputStream);
            return url;
        }
        // 将生成的zip文件内容写入响应流
		/*response.setContentType("application/zip");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + folder + ".zip\"");
		byte[] zipData = bos.toByteArray();
		response.setContentLength(zipData.length);
		response.setBufferSize(4096);
		response.getOutputStream().write(zipData);*/
    }
}
